iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0

上一篇拆解了 Command、Arg 與 Flag 的角色與設計,這篇我們將開始動手建立第一個 CLI 專案。本系列示範使用 VS Code 作為 IDE、Go 為開發語言、Cobra 為 CLI 框架。

💡 範例程式碼
整個系列文章用到的範例程式碼皆收錄在 GitHub 儲存庫:evanchen76/cli-sample。本文對應的範例程式位於 mytool/ 目錄,你可以直接從 GitHub 下載或 clone 到本機對照閱讀:

git clone https://github.com/evanchen76/cli-sample.git

這個系列選擇 Go 與 Cobra 示範 CLI。Go 能編譯成單一執行檔、支援跨平台編譯,也能用 Goroutine 處理併發工作;許多常見的開發工具也用 Go 實作 CLI,例如 Grafana CLIStripe CLIDocker CLIkubectlGitHub CLI。Cobra 則負責解析 Command、arg 與 Flag,並根據命令定義產生 help 與 Shell 自動完成。你也可以使用熟悉的 Node.js、Python 或 Rust,搭配對應的 CLI 框架實作;雖然語法與工具不同,核心概念大致相同,不影響對這個系列的理解。


安裝環境

在終端機輸入以下命令來安裝 go

brew install go

在 VSCode 開一個 Go 專案

安裝 Go 延伸套件:打開 VSCode → Extensions(Cmd+Shift+X)→ 搜尋 Go(Google 官方出的)→ Install。裝完會提示安裝 goplsdlv 等工具,按 Install All 全裝。

建立專案目錄 mytool,接著 初始化 Go module(每個 Go 專案都要做一次,用來管理套件版本):

go mod init mytool

這會產生 go.mod

mytool/         
├── go.mod     

內容如下:

module mytool

go 1.22     ← go版本

接著新增檔案main.go。main.go 是整個命令列應用程式的進入點 。

mytool/
├── main.go
package main

import "mytool/cmd"

func main() {
	cmd.Execute()
}

這段程式碼包含三個關鍵結構:

  • package main:宣告這個檔案屬於 main 套件。Go 編譯器看到 package main 時,會知道這個套件要編譯成獨立執行的執行檔,而不是提供給其他專案引用(import)的函式庫。
  • import "mytool/cmd":匯入專案內部的 cmd 套件。這裡 mytool 是前面在 go.mod 定義的模組名稱,cmd 是存放 CLI 命令邏輯的目錄與套件名稱。
  • func main():程式啟動時的進入點。裡面只有一行 cmd.Execute(),將所有的命令解析與執行邏輯全部交給 cmd 套件處理。這樣的分工能讓 main.go 保持簡潔,將程式進入點與實際命令邏輯解耦。

接著定義根命令,新增 root.go

mytool/
├── main.go          
├── go.mod           
└── cmd/             
    └── root.go      ← 定義根命令 (mytool)

root.go 定義「根命令」—— 也就是 mytool 這個命令本身。Cobra 把所有命令組成一棵樹,根命令是樹根,之後每個子命令(mytool greetmytool login…)都掛在它底下。當使用者只打 mytool、不帶任何子命令時,跑的就是這個根命令。

package cmd

import (
	"github.com/spf13/cobra"
)

// rootCmd 是整棵命令樹的樹根,代表 mytool 這個命令本身。
var rootCmd = &cobra.Command{
	Use:   "mytool",           // 命令名稱,也是使用者在終端機要打的字
	Short: "My first CLI tool", // 一行簡短說明,會出現在 help 訊息最上面
}

// Execute 是對外的進入點,由 main.go 呼叫。
// rootCmd.Execute() 會開始解析使用者輸入的 argv,
// 判斷要跑根命令本身、還是某個子命令。
func Execute() {
	rootCmd.Execute()
}

新增完我們就來執行看看。

go run main.go

結果會印出 Short 上給的內容。因為這個根命令並沒有執行任何動作。

My first CLI tool

除了用 go run來執行,也可以先打包好,再執行mytool。

go build -o mytool
./mytool

到這裡,mytool 的基礎框架與第一次建置已順利完成。如果這是你第一次閱讀 Go 程式碼,我們接著釐清剛才範例中出現的三個語法關鍵:

Go 知識補充:Package、可見性與指標

先講 package,就是 root.go 最上面這行:

package cmd

每個 .go 檔案最上面都要宣告自己屬於哪個 package,main.gopackage main,這裡是 package cmd。同一個 package 裡的檔案可以直接互相呼叫,不用另外 import。

接著是大小寫,對照剛剛程式碼裡的這兩行:

var rootCmd = &cobra.Command{ ... }
func Execute() { ... }

Go 沒有 publicprivate 這種關鍵字,看的是名字第一個字母的大小寫:大寫開頭外面看得到(exported),小寫開頭只有自己這個 package 內部看得到(unexported)。rootCmd 小寫,只有 cmd package 自己用得到;Execute 大寫,所以 main.go 才能跨 package 呼叫 cmd.Execute()

最後是指標(Pointer)。這是從 JavaScript、Python 或 Java 等語言轉過來的開發者容易困惑的地方,對照程式碼裡這行最前面的 &

var rootCmd = &cobra.Command{
	Use:   "mytool",
	Short: "My first CLI tool",
}

在 Go 語言中,所有變數預設都是傳值(Pass-by-Value)。如果沒有加 &,當你把一個 struct 變數傳給其他函式或賦值給新變數時,Go 會在記憶體中複製出一份全新的資料副本。

這行程式碼做了兩件事:

  1. cobra.Command{...}:在記憶體中建立一個 Command 結構體(struct)資料。
  2. & 取址符號:取得這個結構體在記憶體中的記憶體位址(Memory Address),把 rootCmd 變成一個指標變數(型別為 *cobra.Command)。

在 CLI 專案中使用指標有兩個關鍵好處:

  • 確保修改同步生效:Cobra 的命令結構是一棵樹。當我們呼叫 rootCmd.AddCommand(subCmd) 新增子命令,或是綁定 Flag 時,程式必須直接修改 rootCmd 本身的狀態。如果傳的是複製品,任何修改都只會作用在臨時副本上,原本的命令樹完全不受影響。
  • 避免不必要的複製開銷cobra.Command 內部包含許多欄位與設定。傳遞指標只需要傳送一個小小的記憶體位址數字,不必每次複製整個結構體。

簡而言之:在 Go 裡看到 &,代表「我拿的是這份資料在記憶體裡的實際位置,大家共用同一份」;沒加 & 則是「複製一份新副本給自己用」。

mytool 現在只有一個空殼的根命令,什麼事都還做不了。下一篇我們動手加第一個子命令 → [用 Cobra 建立子命令與 Args|建構 CLI 子命令與輸入參數]。


上一篇
命令進入 CLI 程式後:Command、Arg 與 Flag 的拆解與設計
下一篇
建構 CLI 子命令與輸入參數
系列文
30 天學會做一個 CLI:打造人類與 AI 都友善的現代 CLI 應用12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言